﻿/*
 * marqdown
 * --------
 * a C# Markdown processor
 * 
 * Copyright (c) 2010 Andrew Wall
 * http://code.google.com/p/marqdown/
 * 
 * 
 */


#region Copyright and license

/*

Copyright (c) 2010 Andrew Wall

 http://www.opensource.org/licenses/mit-license.php
  
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#endregion


using System.Collections.Generic;
using System.Text;
using System.Linq;
using System;

namespace Marqdown
{
    interface Processor
    {
        void postProcess(List<string> list);
    }

    namespace StateMachine
    {
        using ActionQueue = Queue<ActionSetting>;


        struct LeadingState
        {
            public ConcreteState leading;
            public ActionQueue queue;
        }
        public class Info
        {
            #region Undecided private members
            // Undecided private members
            private Tag parent;
            private Stack<Tag> childTags;
            private StringBuilder currentLine;
            private Stack<LeadingState> leadingStates;
            private Dictionary<string, LinkItem> linkMap;
            #endregion

            internal Tag tag;
            internal string prefix;
            internal char hrChar;
            internal int hrCount;
            internal static ProcessAction nop = ((info, c) => { });
            internal ConcreteState postNestingList;
            internal StringBuilder linkString;
            internal LinkItem linkItem;

            private ConcreteState state;
            private static ConcreteState staticLeading = new LeadingChar();
            private LeadingState leading;

            internal static ConcreteState plain = new PlainText();
            internal static ConcreteState header1 = new Header1();
            internal static ConcreteState blockQuote = new BlockQuote();
            internal static ConcreteState unorderedList = new UnorderedList();
            internal static ConcreteState orderedList = new OrderedList();

            private static CompetitiveState hr = new HorizontalRule();
            private static CompetitiveState hrEol = new HorizontalRuleEOL();
            private static CompetitiveState cb = new CodeBlock();
            private static CompetitiveState ul = new UnorderedList();
            private static CompetitiveState bq = new BlockQuote();
            private static CompetitiveState h1 = new Header1();
            private static CompetitiveState ulh1 = new UnderlineHeader1();
            private static CompetitiveState ulh2 = new UnderlineHeader2();
            private static CompetitiveState ol = new OrderedList();
            private static CompetitiveState prelink = new LinkFootnote();
            private static CompetitiveState link = new LinkLabel();

            private static CompetitiveState nl = new NewLine();
            private static ActionSetting defaultAction = new ActionSetting();
            private ActionDictionary leadingDict;
            private ActionDictionary listsLeadingDict;
            public Info(Dictionary<string, LinkItem> aMap)
            {
                parent = new Tag();
                childTags = new Stack<Tag>();
                currentLine = new StringBuilder();
                leadingStates = new Stack<LeadingState>();
                linkMap = aMap;
                linkString = new StringBuilder();
                linkItem.label = "";
                linkItem.url = "";
                linkItem.title = "";
                leading.leading = staticLeading;
                tag = parent.NewChildTag();
                prefix = "";
                state = staticLeading;
                // state.onEnter(this);

                defaultAction.action = (info, c) => { Info.plain.process(info, c); }; // info.tag.Append(c); };
                defaultAction.state = plain;

                // Prepare competitive states for PlainText.LeadingChar
                leadingDict = new ActionDictionary();

                leadingDict
                    .AddToLeading(ulh2)
                    .AddToLeading(hr)
                    .AddToLeading(hrEol)
                    .AddToLeading(cb)
                    .AddToLeading(prelink)
                    .AddToLeading(link)
                    .AddToLeading(ul)
                    .AddToLeading(bq)
                    .AddToLeading(h1)
                    .AddToLeading(ulh1)
                    .AddToLeading(ol)
                    .AddToLeading(nl)

                // Each of the items in leadingDict must now have plain added.
                    .AddToAll(defaultAction);

                // Prepare competitive states for UnorderedList.LeadingChar
                listsLeadingDict = new ActionDictionary();
                listsLeadingDict.AddToLeading(hr)
                    .AddToLeading(hrEol)
                    .AddToLeading(ul)
                    .AddToLeading(bq)
                    .AddToLeading(h1)
                    .AddToLeading(ol)
                    .AddToLeading(nl)
                    .AddToAll(defaultAction);

            }

            public void process(string text)
            {
                foreach (char c in text)
                {
                    /*
                    switch (c)
                    {
                        case '\n':
                            Console.WriteLine("<<<<<<------");
                            break;
                        default:
                            Console.Write(c);
                            break;
                    }
                    */
                    process(c);
                }
                terminate();
            }

            public void process(char c)
            {
                currentLine.Append(c);
                switch (c)
                {
                    case '\n':
                        state.process(this, c);
                        currentLine.Length = 0;
                        break;
                    default:
                        state.process(this, c);
                        break;
                }
            }
            internal void postProcess(Processor processor)
            {
                getParent().postProcessText(processor);
            }
            private void reprocess(char c)
            {
                state.process(this, c);
            }

            /// <summary>
            /// process param c as leading char
            /// </summary>
            /// <param name="c"></param>
            /// <returns>true if default action was taken</returns>
            internal bool processLeadingChar(char c)
            {
                bool ret = true;
                ActionSetting a = defaultAction;
                if (leadingDict.ContainsKey(c))
                {
                    leading.queue=updateCompetingStates(leadingDict, c);
                    a = leading.queue.Dequeue();
                    ret = false;
                }
                performCompetitiveAction(c, a);
                return ret;
            }
            internal bool processListsLeadingChar(char c)
            {
                bool ret = true;
                ActionSetting a = defaultAction;
                if (listsLeadingDict.ContainsKey(c))
                {
                    leading.queue = updateCompetingStates(listsLeadingDict, c);
                    a = leading.queue.Dequeue();
                    ret = false;
                }
                performCompetitiveAction(c, a);
                return ret;
            }

            private ActionQueue updateCompetingStates(ActionDictionary dict, char c)
            {
                ActionQueue states = new ActionQueue();
                var list = dict.get(c);
                foreach (var item in list)
                {
                    states.Enqueue(item);
                }
                return states;
            }
            private void performCompetitiveAction(char c, ActionSetting a)
            {
                a.action(this, c);
                ChangeState(a.state);
            }
            internal void competitorFailed()
            {
                if (leading.queue.Count>0)
                {
                    ActionSetting a = leading.queue.Dequeue();

                    Action<char> a2 = ((c) => process(c));
                    Action<char> a1 = ((c) =>
                    {
                        currentLine.Append(c);
                        performCompetitiveAction(c, a);
                        a1 = a2;
                    });

                    // @todo: This may fail to deliver all chars because
                    // currentLine keeps getting destroyed.
                    string all = currentLine.ToString();
                    currentLine.Length = 0;
                    foreach (char c in all)
                    {
                        a1(c);
                    }
                }
                else
                {
                    throw new ArgumentException();
                }
            }

            internal int currentSize()
            {
                return currentLine.Length;
            }
            internal string currentLineValue()
            {
                return currentLine.ToString();
            }
            internal void ChangeState(ConcreteState newState)
            {
                state.onExit(this);
                state = newState;
                state.onEnter(this);
            }
            internal void ChangeStateToLeading()
            {
                ChangeState(leading.leading);
            }
            internal void terminate()
            {
                state.terminate(this);
            }

            private Tag getParent()
            {
                Tag parentTag = parent;
                if (childTags.Count > 0)
                {
                    //Console.WriteLine("child");
                    parentTag = childTags.ElementAt(0);
                }
                else
                {
                    //Console.WriteLine("parent");
                }
                return parentTag;
            }
            public override string ToString()
            {
                var output = new StringBuilder();
                Tag parentTag = getParent();
                parentTag.RenderTags(output);
                return output.ToString();
            }


            internal void NewTag()
            {
                tag = parent.NewChildTag();
                tag.SetPrefix(prefix);
            }
            internal void NewChildTag()
            {
                childTags.Push(parent);
                parent = tag;
                tag = parent.NewChildTag();
                tag.SetPrefix(prefix);
            }
            internal void PopChildTag()
            {
                tag = parent;
                parent = childTags.Pop();
            }

            internal void setParentToLoose()
            {
                parent.setChildrenToLoose();
            }
            internal bool isParentLoose()
            {
                return parent.areChildrenLoose();
            }

            internal void ResendAsLeading(char c)
            {
                ChangeState(leading.leading);
                reprocess(c);
            }


            internal static string digits = "0123456789";
            internal bool isDigit(char c)
            {
                return digits.Contains(c);
            }

            /// <summary>
            /// ResendAllAsPlain is a way of bypassing LeadingChar
            /// so that the first char is not decoded again as a
            /// leading char.  This is because an 'error' was detected
            /// when in the format and really the whole line should be
            /// plain text.  eg "1 dozen" is not an ordered list.
            /// @TODO: this will be made redundant by CompetitiveStates.
            ///     Currently not used, but only because there are not enough tests.
            /// </summary>
            internal void ResendAllAsPlain()
            {
                ChangeState(Info.plain);
                ResendAll();
            }

            internal void ResendAll()
            {
                string all = currentLine.ToString();
                currentLine.Length = 0;
                foreach (char c in all)
                {
                    process(c);
                }
            }

            internal void PushNewLeadingState(ConcreteState state)
            {
                leadingStates.Push(leading);
                leading.leading = state;
                ChangeState(state);
            }

            internal void PopLeading()
            {
                leading = leadingStates.Pop();
            }
            internal bool isNestedList()
            {
                return leadingStates.Count > 0;
            }

            internal int ChildTagSize()
            {
                return childTags.Count;
            }

            internal void AddToLinkMap()
            {
                if (!linkMap.ContainsKey(linkItem.label))
                {
                    linkMap.Add(linkItem.label, linkItem);
                }
            }
        }
    }
}
